use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::{BuildMigrateError, Result};

#[cfg(target_os = "windows")]
use std::process::Command;

/// 脚本执行器，负责运行迁移前脚本并捕获环境变量
pub struct ScriptRunner {
    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
    working_directory: Option<PathBuf>,
    continue_on_error: bool,
}

impl ScriptRunner {
    /// 创建新的脚本执行器
    pub fn new(working_directory: Option<PathBuf>, continue_on_error: bool) -> Self {
        Self {
            working_directory,
            continue_on_error,
        }
    }

    /// 执行所有脚本并返回捕获的环境变量
    ///
    /// # 参数
    /// * `scripts` - 脚本文件路径列表
    ///
    /// # 返回值
    /// 返回所有脚本执行后的环境变量映射
    pub fn run_scripts(&self, scripts: &[PathBuf]) -> Result<HashMap<String, String>> {
        if scripts.is_empty() {
            log::debug!("没有需要执行的迁移前脚本");
            return Ok(HashMap::new());
        }

        log::info!("开始执行 {} 个迁移前脚本", scripts.len());

        let mut env_vars = HashMap::new();

        for (index, script_path) in scripts.iter().enumerate() {
            log::info!("执行脚本 {}/{}: {}", index + 1, scripts.len(), script_path.display());

            match self.run_single_script(script_path) {
                Ok(vars) => {
                    log::info!("脚本执行成功，捕获了 {} 个环境变量", vars.len());
                    // 合并环境变量（后执行的脚本会覆盖前面的同名变量）
                    env_vars.extend(vars);
                }
                Err(e) => {
                    let error_msg = format!("脚本执行失败: {}: {}", script_path.display(), e);
                    log::error!("{}", error_msg);

                    if !self.continue_on_error {
                        return Err(e);
                    }
                    log::warn!("continue_on_error 已启用，继续执行剩余脚本");
                }
            }
        }

        log::info!("所有脚本执行完毕，共捕获 {} 个环境变量", env_vars.len());
        Ok(env_vars)
    }

    /// 执行单个脚本并捕获环境变量
    fn run_single_script(&self, script_path: &Path) -> Result<HashMap<String, String>> {
        // 确保脚本文件存在
        if !script_path.exists() {
            return Err(BuildMigrateError::ScriptExecution(
                format!("脚本文件不存在: {}", script_path.display())
            ));
        }

        // 根据平台选择执行方式
        #[cfg(target_os = "windows")]
        {
            // 确定工作目录
            let working_dir = self.determine_working_directory(script_path)?;

            // 获取脚本的绝对路径
            let absolute_script_path = if script_path.is_absolute() {
                script_path.to_path_buf()
            } else {
                std::env::current_dir()?.join(script_path)
            };

            self.run_windows_script(&absolute_script_path, &working_dir)
        }

        #[cfg(not(target_os = "windows"))]
        {
            // 避免未使用变量的警告
            let _ = script_path;
            // 非 Windows 平台暂不支持
            Err(BuildMigrateError::ScriptExecution(
                "当前仅支持在 Windows 平台执行批处理脚本".to_string()
            ))
        }
    }

    /// 在 Windows 上执行批处理脚本并捕获环境变量
    #[cfg(target_os = "windows")]
    fn run_windows_script(&self, script_path: &Path, working_dir: &Path) -> Result<HashMap<String, String>> {
        use std::io::Write;
        use std::time::{SystemTime, UNIX_EPOCH};

        // 规范化路径，确保使用 Windows 格式的反斜杠
        let script_path_normalized = script_path.to_string_lossy().replace('/', "\\");

        log::debug!("执行脚本: {}", script_path_normalized);
        log::debug!("工作目录: {}", working_dir.display());

        // 创建临时包装脚本来避免引号转义问题
        // 使用纳秒时间戳确保文件名唯一，避免并发测试冲突
        let temp_dir = std::env::temp_dir();
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let wrapper_script = temp_dir.join(format!("build_migrate_wrapper_{}_{}.bat",
            std::process::id(), timestamp));

        // 写入包装脚本
        {
            let mut file = std::fs::File::create(&wrapper_script)
                .map_err(|e| BuildMigrateError::ScriptExecution(
                    format!("创建临时脚本失败: {}", e)
                ))?;

            // 包装脚本需要：
            // 1. 调用用户脚本
            // 2. 保存脚本的退出码
            // 3. 输出所有环境变量（包括退出码）
            // 4. 返回脚本的退出码
            writeln!(file, "@echo off")?;
            writeln!(file, "call \"{}\"", script_path_normalized)?;
            writeln!(file, "set SCRIPT_EXIT_CODE=%ERRORLEVEL%")?;
            writeln!(file, "set")?;
            writeln!(file, "exit /b %SCRIPT_EXIT_CODE%")?;
        }

        log::debug!("临时包装脚本: {}", wrapper_script.display());

        // 执行包装脚本
        let output = Command::new(&wrapper_script)
            .current_dir(working_dir)
            .output()
            .map_err(|e| BuildMigrateError::ScriptExecution(
                format!("执行脚本失败: {}", e)
            ))?;

        // 清理临时文件
        let _ = std::fs::remove_file(&wrapper_script);

        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        log::debug!("退出码: {:?}", output.status.code());
        log::debug!("标准输出长度: {}", stdout.len());
        log::debug!("标准输出前500字符:\n{}", &stdout.chars().take(500).collect::<String>());
        if !stderr.is_empty() {
            log::debug!("错误输出: {}", stderr);
        }

        // 检查脚本是否执行成功
        if !output.status.success() {
            return Err(BuildMigrateError::ScriptExecution(
                format!("脚本执行返回非零退出码: {}\n标准输出: {}\n错误输出: {}",
                    output.status.code().unwrap_or(-1),
                    stdout,
                    stderr)
            ));
        }

        // 解析环境变量
        let env_vars = self.parse_windows_env_output(&stdout)?;

        log::debug!("成功解析 {} 个环境变量", env_vars.len());

        // 如果标准输出不为空但没有解析到任何环境变量，记录警告
        if !stdout.is_empty() && env_vars.is_empty() {
            log::warn!("脚本有输出但未解析到环境变量。输出内容:\n{}", stdout);
        }

        Ok(env_vars)
    }

    /// 解析 Windows set 命令的输出
    #[cfg(target_os = "windows")]
    fn parse_windows_env_output(&self, output: &str) -> Result<HashMap<String, String>> {
        let mut env_vars = HashMap::new();

        for line in output.lines() {
            // 跳过空行
            if line.trim().is_empty() {
                continue;
            }

            // 查找第一个等号的位置
            if let Some(eq_pos) = line.find('=') {
                let key = line[..eq_pos].trim().to_string();
                let value = line[eq_pos + 1..].to_string();

                // 只保存非空的环境变量名
                if !key.is_empty() {
                    env_vars.insert(key, value);
                }
            }
        }

        Ok(env_vars)
    }

    /// 确定脚本执行的工作目录
    #[cfg(target_os = "windows")]
    fn determine_working_directory(&self, script_path: &Path) -> Result<PathBuf> {
        if let Some(ref wd) = self.working_directory {
            // 使用配置的工作目录
            Ok(wd.clone())
        } else if let Some(parent) = script_path.parent() {
            // 使用脚本所在目录
            if parent.as_os_str().is_empty() {
                // 如果是相对路径且没有父目录，使用当前目录
                Ok(std::env::current_dir()?)
            } else {
                Ok(parent.to_path_buf())
            }
        } else {
            // 使用当前工作目录
            Ok(std::env::current_dir()?)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[cfg(target_os = "windows")]
    fn test_parse_windows_env_output() {
        let runner = ScriptRunner::new(None, false);
        let output = r#"PATH=C:\Windows\System32
USERPROFILE=C:\Users\Test
MY_VAR=test_value
EMPTY_VAR=
"#;

        let env_vars = runner.parse_windows_env_output(output).unwrap();

        assert_eq!(env_vars.get("PATH"), Some(&"C:\\Windows\\System32".to_string()));
        assert_eq!(env_vars.get("USERPROFILE"), Some(&"C:\\Users\\Test".to_string()));
        assert_eq!(env_vars.get("MY_VAR"), Some(&"test_value".to_string()));
        assert_eq!(env_vars.get("EMPTY_VAR"), Some(&"".to_string()));
    }

    #[test]
    #[cfg(target_os = "windows")]
    fn test_parse_env_with_equals_in_value() {
        let runner = ScriptRunner::new(None, false);
        let output = "MY_VAR=key=value\n";

        let env_vars = runner.parse_windows_env_output(output).unwrap();

        assert_eq!(env_vars.get("MY_VAR"), Some(&"key=value".to_string()));
    }
}
